/*
 ------------------------------------------------------------------------------
 Copyright (C) 2006-2007 Team Blur.

 This file is part of the Quantum Engine source code.

 The Quantum Engine source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Quantum Engine source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Quantum Engine source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


#include "r_local.h"


/*
 ==================
 R_LoadPlanes
 ==================
*/
static void R_LoadPlanes (const byte *data, const bspLump_t *lump){

	bspPlane_t	*in;
	odPlane		*out;
	int			i;

	in = (bspPlane_t *)(data + lump->offset);
	if (lump->length % sizeof(bspPlane_t))
		Com_Error(false, "R_LoadMap: funny lump size (%s)", tr.world->name);

	tr.world->numPlanes = lump->length / sizeof(bspPlane_t);
	if (tr.world->numPlanes < 1)
		Com_Error(false, "R_LoadMap: no planes (%s)", tr.world->name);
	else if (tr.world->numPlanes > BSP_MAX_PLANES)
		Com_Error(false, "R_LoadMap: too many planes (%s)", tr.world->name);

	tr.world->planes = out = (odPlane *)Mem_Alloc(tr.world->numPlanes * sizeof(odPlane), TAG_RENDERER);
	tr.world->size += tr.world->numPlanes * sizeof(odPlane);

	for (i = 0; i < tr.world->numPlanes; i++, in++, out++){
		out->normal[0] = LittleFloat(in->normal[0]);
		out->normal[1] = LittleFloat(in->normal[1]);
		out->normal[2] = LittleFloat(in->normal[2]);

		out->dist = LittleFloat(in->dist);

		out->SetType();
		out->SetSignBits();
	}
}

/*
 ==================
 R_LoadIndices
 ==================
*/
static void R_LoadIndices (const byte *data, const bspLump_t *lump){

	int			*in;
	glIndex_t	*out;
	int			i;

	in = (int *)(data + lump->offset);
	if (lump->length % sizeof(int))
		Com_Error(false, "R_LoadMap: funny lump size (%s)", tr.world->name);

	tr.world->numIndices = lump->length / sizeof(int);
	if (tr.world->numIndices < 1)
		Com_Error(false, "R_LoadMap: no indices (%s)", tr.world->name);
	else if (tr.world->numIndices > BSP_MAX_INDICES)
		Com_Error(false, "R_LoadMap: too many indices (%s)", tr.world->name);

	tr.world->indices = out = (glIndex_t *)Mem_Alloc(tr.world->numIndices * sizeof(glIndex_t), TAG_RENDERER);
	tr.world->size += tr.world->numIndices * sizeof(glIndex_t);

	for (i = 0; i < tr.world->numIndices; i++, in++, out++)
		*out = LittleLong(*in);
}

/*
 ==================
 R_LoadVertices
 ==================
*/
static void R_LoadVertices (const byte *data, const bspLump_t *lump){

	bspVertex_t	*in;
	glVertex_t	*out;
	int			i;

	in = (bspVertex_t *)(data + lump->offset);
	if (lump->length % sizeof(bspVertex_t))
		Com_Error(false, "R_LoadMap: funny lump size (%s)", tr.world->name);

	tr.world->numVertices = lump->length / sizeof(bspVertex_t);
	if (tr.world->numVertices < 1)
		Com_Error(false, "R_LoadMap: no vertices (%s)", tr.world->name);
	else if (tr.world->numVertices > BSP_MAX_VERTICES)
		Com_Error(false, "R_LoadMap: too many vertices (%s)", tr.world->name);

	tr.world->vertices = out = (glVertex_t *)Mem_Alloc(tr.world->numVertices * sizeof(glVertex_t), TAG_RENDERER);
	tr.world->size += tr.world->numVertices * sizeof(glVertex_t);

	for (i = 0; i < tr.world->numVertices; i++, in++, out++){
		out->xyz[0] = LittleFloat(in->xyz[0]);
		out->xyz[1] = LittleFloat(in->xyz[1]);
		out->xyz[2] = LittleFloat(in->xyz[2]);

		out->normal[0] = LittleFloat(in->normal[0]);
		out->normal[1] = LittleFloat(in->normal[1]);
		out->normal[2] = LittleFloat(in->normal[2]);

		out->tangents[0][0] = LittleFloat(in->tangents[0][0]);
		out->tangents[0][1] = LittleFloat(in->tangents[0][1]);
		out->tangents[0][2] = LittleFloat(in->tangents[0][2]);
		out->tangents[1][0] = LittleFloat(in->tangents[1][0]);
		out->tangents[1][1] = LittleFloat(in->tangents[1][1]);
		out->tangents[1][2] = LittleFloat(in->tangents[1][2]);

		out->st[0] = LittleFloat(in->st[0]);
		out->st[1] = LittleFloat(in->st[1]);

		out->color[0] = in->color[0];
		out->color[1] = in->color[1];
		out->color[2] = in->color[2];
		out->color[3] = in->color[3];
	}
}

/*
 ==================
 R_LoadNeighbors
 ==================
*/
static void R_LoadNeighbors (const byte *data, const bspLump_t *lump){

	int		*in;
	int		*out;
	int		i;

	in = (int *)(data + lump->offset);
	if (lump->length % sizeof(int))
		Com_Error(false, "R_LoadMap: funny lump size (%s)", tr.world->name);

	tr.world->numNeighbors = lump->length / sizeof(int);
	if (tr.world->numNeighbors < 1)
		Com_Error(false, "R_LoadMap: no neighbors (%s)", tr.world->name);
	else if (tr.world->numNeighbors > BSP_MAX_NEIGHBORS)
		Com_Error(false, "R_LoadMap: too many neighbors (%s)", tr.world->name);

	tr.world->neighbors = out = (int *)Mem_Alloc(tr.world->numNeighbors * sizeof(int), TAG_RENDERER);
	tr.world->size += tr.world->numNeighbors * sizeof(int);

	for (i = 0; i < tr.world->numNeighbors; i++, in++, out++)
		*out = LittleLong(*in);
}

/*
 ==================
 R_LoadFacePlanes
 ==================
*/
static void R_LoadFacePlanes (const byte *data, const bspLump_t *lump){

	bspPlane_t	*in;
	odPlane		*out;
	int			i;

	in = (bspPlane_t *)(data + lump->offset);
	if (lump->length % sizeof(bspPlane_t))
		Com_Error(false, "R_LoadMap: funny lump size (%s)", tr.world->name);

	tr.world->numFacePlanes = lump->length / sizeof(bspPlane_t);
	if (tr.world->numFacePlanes < 1)
		return;
	else if (tr.world->numFacePlanes > BSP_MAX_FACEPLANES)
		Com_Error(false, "R_LoadMap: too many face planes (%s)", tr.world->name);

	tr.world->facePlanes = out = (odPlane *)Mem_Alloc(tr.world->numFacePlanes * sizeof(odPlane), TAG_RENDERER);
	tr.world->size += tr.world->numFacePlanes * sizeof(odPlane);

	for (i = 0; i < tr.world->numFacePlanes; i++, in++, out++){
		out->normal[0] = LittleFloat(in->normal[0]);
		out->normal[1] = LittleFloat(in->normal[1]);
		out->normal[2] = LittleFloat(in->normal[2]);

		out->dist = LittleFloat(in->dist);

		out->SetType();
		out->SetSignBits();
	}
}

/*
 ==================
 R_LoadFoliageInstances
 ==================
*/
static void R_LoadFoliageInstances (const byte *data, const bspLump_t *lump){

	bspFoliageInstance_t	*in;
	foliageInstance_t		*out;
	odAngles				angles;
	int						i;

	in = (bspFoliageInstance_t *)(data + lump->offset);
	if (lump->length % sizeof(bspFoliageInstance_t))
		Com_Error(false, "R_LoadMap: funny lump size (%s)", tr.world->name);

	tr.world->numFoliageInstances = lump->length / sizeof(bspFoliageInstance_t);
	if (tr.world->numFoliageInstances < 1)
		return;
	else if (tr.world->numFoliageInstances > BSP_MAX_FOLIAGEINSTANCES)
		Com_Error(false, "R_LoadMap: too many foliage instances (%s)", tr.world->name);

	tr.world->foliageInstances = out = (foliageInstance_t *)Mem_Alloc(tr.world->numFoliageInstances * sizeof(foliageInstance_t), TAG_RENDERER);
	tr.world->size += tr.world->numFoliageInstances * sizeof(foliageInstance_t);

	for (i = 0; i < tr.world->numFoliageInstances; i++, in++, out++){
		out->origin[0] = LittleFloat(in->origin[0]);
		out->origin[1] = LittleFloat(in->origin[1]);
		out->origin[2] = LittleFloat(in->origin[2]);

		angles[0] = LittleFloat(in->angles[0]);
		angles[1] = LittleFloat(in->angles[1]);
		angles[2] = LittleFloat(in->angles[2]);

		out->axis = angles.ToMat3();

		out->parentSurface = NULL;

		// Clear counter
		out->viewCount = 0;
	}
}

/*
 ==================
 R_LoadSurfaces
 ==================
*/
static void R_LoadSurfaces (const byte *data, const bspLump_t *lump, const bspLump_t *materialLump){

	bspSurface_t	*in;
	bspMaterial_t	*materials;
	surface_t		*out;
	int				i, j, num;

	in = (bspSurface_t *)(data + lump->offset);
	if (lump->length % sizeof(bspSurface_t))
		Com_Error(false, "R_LoadMap: funny lump size (%s)", tr.world->name);

	materials = (bspMaterial_t *)(data + materialLump->offset);
	if (materialLump->length % sizeof(bspMaterial_t))
		Com_Error(false, "R_LoadMap: funny lump size (%s)", tr.world->name);

	tr.world->numSurfaces = lump->length / sizeof(bspSurface_t);
	if (tr.world->numSurfaces < 1)
		Com_Error(false, "R_LoadMap: no surfaces (%s)", tr.world->name);
	else if (tr.world->numSurfaces > BSP_MAX_SURFACES)
		Com_Error(false, "R_LoadMap: too many surfaces (%s)", tr.world->name);

	tr.world->surfaces = out = (surface_t *)Mem_Alloc(tr.world->numSurfaces * sizeof(surface_t), TAG_RENDERER);
	tr.world->size += tr.world->numSurfaces * sizeof(surface_t);

	for (i = 0; i < tr.world->numSurfaces; i++, in++, out++){
		out->type = (surfaceType_t)LittleLong(in->type);

		out->material = R_FindMaterial(materials[LittleLong(in->materialNum)].name, MT_GENERIC);

		num = LittleLong(in->planeNum);
		if (num != -1)
			out->plane = tr.world->planes + num;
		else
			out->plane = NULL;

		for (j = 0; j < 3; j++){
			out->bounds[0][j] = LittleFloat(in->bounds[0][j]);
			out->bounds[1][j] = LittleFloat(in->bounds[1][j]);
		}

		out->numIndices = LittleLong(in->numIndices);
		out->indices = tr.world->indices + LittleLong(in->firstIndex);

		out->numVertices = LittleLong(in->numVertices);
		out->vertices = tr.world->vertices + LittleLong(in->firstVertex);

		out->neighbors = tr.world->neighbors + LittleLong(in->firstNeighbor);

		num = LittleLong(in->firstFacePlane);
		if (num != -1)
			out->facePlanes = tr.world->facePlanes + num;
		else
			out->facePlanes = NULL;

		// Foliage surfaces are special
		out->numFoliageInstances = LittleLong(in->numFoliageInstances);

		num = LittleLong(in->firstFoliageInstance);
		if (num != -1){
			out->foliageInstances = tr.world->foliageInstances + num;

			// Set parent surface
			for (j = 0; j < out->numFoliageInstances; j++)
				out->foliageInstances[j].parentSurface = out;
		}
		else
			out->foliageInstances = NULL;

		out->foliageRadius = LittleFloat(in->foliageRadius);
		out->foliageMinDistance = LittleFloat(in->foliageMinDistance);
		out->foliageMaxDistance = LittleFloat(in->foliageMaxDistance);

		// Clear the vertex buffers
		out->ambientBuffer = NULL;
		out->shadowBuffer = NULL;

		out->ambientIndex = 0;
		out->shadowIndex = 0;

		// Clear counters
		out->viewCount = 0;
		out->worldCount = 0;
		out->lightCount = 0;
		out->fragmentCount = 0;
	}
}

/*
 ==================
 R_LoadLeafSurfaces
 ==================
*/
static void R_LoadLeafSurfaces (const byte *data, const bspLump_t *lump){

	int			*in;
	surface_t	**out;
	int			i;

	in = (int *)(data + lump->offset);
	if (lump->length % sizeof(int))
		Com_Error(false, "R_LoadMap: funny lump size (%s)", tr.world->name);

	tr.world->numLeafSurfaces = lump->length / sizeof(int);
	if (tr.world->numLeafSurfaces < 1)
		Com_Error(false, "R_LoadMap: no leaf surfaces (%s)", tr.world->name);
	else if (tr.world->numLeafSurfaces > BSP_MAX_LEAFSURFACES)
		Com_Error(false, "R_LoadMap: too many leaf surfaces (%s)", tr.world->name);

	tr.world->leafSurfaces = out = (surface_t **)Mem_Alloc(tr.world->numLeafSurfaces * sizeof(surface_t *), TAG_RENDERER);
	tr.world->size += tr.world->numLeafSurfaces * sizeof(surface_t *);

	for (i = 0; i < tr.world->numLeafSurfaces; i++)
		out[i] = tr.world->surfaces + LittleLong(in[i]);
}

/*
 ==================
 R_SetParent
 ==================
*/
static void R_SetParent (node_t *node, node_t *parent){

	node->parent = parent;
	if (node->isLeaf)
		return;

	R_SetParent(node->children[0], node);
	R_SetParent(node->children[1], node);
}

/*
 ==================
 R_LoadNodesAndLeafs
 ==================
*/
static void R_LoadNodesAndLeafs (const byte *data, const bspLump_t *nodeLump, const bspLump_t *leafLump){

	bspNode_t	*inNode;
	bspLeaf_t	*inLeaf;
	node_t		*out;
	int			numNodes, numLeafs;
	int			i, j, num;

	inNode = (bspNode_t *)(data + nodeLump->offset);
	if (nodeLump->length % sizeof(bspNode_t))
		Com_Error(false, "R_LoadMap: funny lump size (%s)", tr.world->name);

	inLeaf = (bspLeaf_t *)(data + leafLump->offset);
	if (leafLump->length % sizeof(bspLeaf_t))
		Com_Error(false, "R_LoadMap: funny lump size (%s)", tr.world->name);

	numNodes = nodeLump->length / sizeof(bspNode_t);
	if (numNodes < 1)
		Com_Error(false, "R_LoadMap: no nodes (%s)", tr.world->name);
	else if (numNodes > BSP_MAX_NODES)
		Com_Error(false, "R_LoadMap: too many nodes (%s)", tr.world->name);

	numLeafs = leafLump->length / sizeof(bspLeaf_t);
	if (numLeafs < 1)
		Com_Error(false, "R_LoadMap: no leafs (%s)", tr.world->name);
	else if (numLeafs > BSP_MAX_LEAFS)
		Com_Error(false, "R_LoadMap: too many leafs (%s)", tr.world->name);

	tr.world->numNodes = numNodes + numLeafs;
	tr.world->nodes = out = (node_t *)Mem_Alloc(tr.world->numNodes * sizeof(node_t), TAG_RENDERER);
	tr.world->size += tr.world->numNodes * sizeof(node_t);

	// Load nodes
	for (i = 0; i < numNodes; i++, inNode++, out++){
		out->isLeaf = false;

		for (j = 0; j < 3; j++){
			out->bounds[0][j] = (float)LittleLong(inNode->bounds[0][j]);
			out->bounds[1][j] = (float)LittleLong(inNode->bounds[1][j]);
		}

		out->parent = NULL;

		// Clear counters
		out->viewCount = 0;
		out->visCount = 0;

		// Node specific
		for (j = 0; j < 2; j++){
			num = LittleLong(inNode->children[j]);
			if (num >= 0)
				out->children[j] = tr.world->nodes + num;
			else
				out->children[j] = tr.world->nodes + numNodes + (-1 - num);
		}

		out->plane = tr.world->planes + LittleLong(inNode->planeNum);

		// Leaf specific
		out->cluster = 0;
		out->area = 0;

		out->numLeafSurfaces = 0;
		out->leafSurfaces = NULL;
	}

	// Load leafs
	for (i = 0; i < numLeafs; i++, inLeaf++, out++){
		out->isLeaf = true;

		for (j = 0; j < 3; j++){
			out->bounds[0][j] = (float)LittleLong(inLeaf->bounds[0][j]);
			out->bounds[1][j] = (float)LittleLong(inLeaf->bounds[1][j]);
		}

		out->parent = NULL;

		// Clear counters
		out->viewCount = 0;
		out->visCount = 0;

		// Node specific
		out->children[0] = NULL;
		out->children[1] = NULL;

		out->plane = NULL;

		// Leaf specific
		out->cluster = LittleLong(inLeaf->cluster);
		out->area = LittleLong(inLeaf->area);

		out->numLeafSurfaces = LittleLong(inLeaf->numLeafSurfaces);
		out->leafSurfaces = tr.world->leafSurfaces + LittleLong(inLeaf->firstLeafSurface);
	}

	// Set parents
	R_SetParent(tr.world->nodes, NULL);
}

/*
 ==================
 R_LoadInlineModels
 ==================
*/
static void R_LoadInlineModels (const byte *data, const bspLump_t *lump){

	bspInlineModel_t	*in;
	inlineModel_t		*out;
	int					i, j;

	in = (bspInlineModel_t *)(data + lump->offset);
	if (lump->length % sizeof(bspInlineModel_t))
		Com_Error(false, "R_LoadMap: funny lump size (%s)", tr.world->name);

	tr.world->numInlineModels = lump->length / sizeof(bspInlineModel_t);
	if (tr.world->numInlineModels < 1)
		Com_Error(false, "R_LoadMap: no inline models (%s)", tr.world->name);
	else if (tr.world->numInlineModels > BSP_MAX_INLINEMODELS)
		Com_Error(false, "R_LoadMap: too many inline models (%s)", tr.world->name);

	tr.world->inlineModels = out = (inlineModel_t *)Mem_Alloc(tr.world->numInlineModels * sizeof(inlineModel_t), TAG_RENDERER);
	tr.world->size += tr.world->numInlineModels * sizeof(inlineModel_t);

	for (i = 0; i < tr.world->numInlineModels; i++, in++, out++){
		for (j = 0; j < 3; j++){
			out->bounds[0][j] = LittleFloat(in->bounds[0][j]);
			out->bounds[1][j] = LittleFloat(in->bounds[1][j]);
		}

		out->numSurfaces = LittleLong(in->numSurfaces);
		out->surfaces = tr.world->surfaces + LittleLong(in->firstSurface);

		// Load the inline model
		R_LoadInlineModel(i, out);
	}
}

/*
 ==================
 R_CacheAmbientVertices
 ==================
*/
static void R_CacheAmbientVertices (void){

	surface_t		*surface;
	vertexBuffer_t	*vertexBuffer;
	int				index, count[MAX_MATERIALS];
	int				i, j;

	if (!r_vertexBuffers->integerValue)
		return;

	// Count vertices for each vertex buffer
	memset(count, 0, sizeof(count));

	for (i = 0, surface = tr.world->surfaces; i < tr.world->numSurfaces; i++, surface++){
		if (surface->type == ST_FOLIAGE)
			continue;		// Not an actual surface

		if (!surface->material->numStages)
			continue;		// Invisible surface

		if (surface->material->deform != DFRM_NONE)
			continue;		// Dynamically deformed surface

		count[surface->material->index] += surface->numVertices;
	}

	// Generate the vertex buffers
	for (i = 0; i < MAX_MATERIALS; i++){
		if (!count[i])
			continue;

		vertexBuffer = R_AllocVertexBuffer(false, NULL, count[i] * sizeof(glVertex_t));

		if (!vertexBuffer)
			continue;

		// Cache all the surfaces
		index = 0;

		for (j = 0, surface = tr.world->surfaces; j < tr.world->numSurfaces; j++, surface++){
			if (surface->material->index != i)
				continue;

			surface->ambientBuffer = vertexBuffer;
			surface->ambientIndex = index;

			// Cache all the vertices
			R_UpdateVertexBuffer(vertexBuffer, surface->vertices, surface->numVertices * sizeof(glVertex_t), index * sizeof(glVertex_t));

			index += surface->numVertices;
		}
	}
}

/*
 ==================
 R_CacheShadowVertices
 ==================
*/
static void R_CacheShadowVertices (void){

	surface_t			*surface;
	vertexBuffer_t		*vertexBuffer;
	glShadowVertex_t	*vertices;
	int					index, count[MAX_MATERIALS];
	int					i, j, k;

	if (!r_vertexBuffers->integerValue)
		return;

	// Count vertices for each vertex buffer
	memset(count, 0, sizeof(count));

	for (i = 0, surface = tr.world->surfaces; i < tr.world->numSurfaces; i++, surface++){
		if (surface->type == ST_FOLIAGE)
			continue;		// Not an actual surface

		if (surface->material->flags & MF_NOSHADOWS)
			continue;		// No shadow-casting surface

		count[surface->material->index] += surface->numVertices;
	}

	// Generate the vertex buffers
	for (i = 0; i < MAX_MATERIALS; i++){
		if (!count[i])
			continue;

		vertexBuffer = R_AllocVertexBuffer(false, NULL, count[i] * 2 * sizeof(glShadowVertex_t));

		if (!vertexBuffer)
			continue;

		// Cache all the surfaces
		vertices = (glShadowVertex_t *)R_MapVertexBuffer(vertexBuffer);

		index = 0;

		for (j = 0, surface = tr.world->surfaces; j < tr.world->numSurfaces; j++, surface++){
			if (surface->material->index != i)
				continue;

			surface->shadowBuffer = vertexBuffer;
			surface->shadowIndex = index;

			// Cache all the vertices
			if (vertices){
				for (k = 0; k < surface->numVertices; k++){
					vertices[0].xyzw[0] = surface->vertices[k].xyz[0];
					vertices[0].xyzw[1] = surface->vertices[k].xyz[1];
					vertices[0].xyzw[2] = surface->vertices[k].xyz[2];
					vertices[0].xyzw[3] = 1.0f;

					vertices[1].xyzw[0] = surface->vertices[k].xyz[0];
					vertices[1].xyzw[1] = surface->vertices[k].xyz[1];
					vertices[1].xyzw[2] = surface->vertices[k].xyz[2];
					vertices[1].xyzw[3] = 0.0f;

					vertices += 2;
				}
			}

			index += surface->numVertices * 2;
		}

		R_UnmapVertexBuffer(vertexBuffer);
	}
}

/*
 ==================
 R_LoadMap
 ==================
*/
void R_LoadMap (const char *name){

	bspHeader_t	*header;
	byte		*data;
	int			i;

	// Check if already loaded
	if (tr.world){
		if (!Str_ICompare(tr.world->name, name))
			return;

		Com_Error(false, "R_LoadMap: can't load '%s' because another world map is already loaded", name);
	}

	// Load the file
	FS_ReadFile(name, (void **)&data);
	if (!data)
		Com_Error(false, "R_LoadMap: '%s' not found", name);

	tr.world = (world_t *)Mem_Alloc(sizeof(world_t), TAG_RENDERER);

	// Fill it in
	Str_Copy(tr.world->name, name, sizeof(tr.world->name));
	tr.world->size = 0;

	// Byte swap the header fields and sanity check
	header = (bspHeader_t *)data;

	for (i = 0; i < sizeof(bspHeader_t) / 4; i++)
		((int *)header)[i] = LittleLong(((int *)header)[i]);

	if (header->id != BSP_IDENT)
		Com_Error(false, "R_LoadMap: wrong file id (%s)", tr.world->name);

	if (header->version != BSP_VERSION)
		Com_Error(false, "R_LoadMap: wrong version number (%i should be %i) (%s)", header->version, BSP_VERSION, tr.world->name);

	// Load into heap
	R_LoadPlanes(data, &header->lumps[LUMP_PLANES]);
	R_LoadIndices(data, &header->lumps[LUMP_INDICES]);
	R_LoadVertices(data, &header->lumps[LUMP_VERTICES]);
	R_LoadNeighbors(data, &header->lumps[LUMP_NEIGHBORS]);
	R_LoadFacePlanes(data, &header->lumps[LUMP_FACEPLANES]);
	R_LoadFoliageInstances(data, &header->lumps[LUMP_FOLIAGEINSTANCES]);
	R_LoadSurfaces(data, &header->lumps[LUMP_SURFACES], &header->lumps[LUMP_MATERIALS]);
	R_LoadLeafSurfaces(data, &header->lumps[LUMP_LEAFSURFACES]);
	R_LoadNodesAndLeafs(data, &header->lumps[LUMP_NODES], &header->lumps[LUMP_LEAFS]);
	R_LoadInlineModels(data, &header->lumps[LUMP_INLINEMODELS]);

	// Free file data
	FS_FreeFile(data);

	// Try to cache ambient and shadow vertices in static vertex buffers
	R_CacheAmbientVertices();
	R_CacheShadowVertices();
}
